using System;
#if CLIENT
using System.DHTML;
#endif

namespace Saltarelle.UI {
#if CLIENT
	[Record]
	internal sealed class DefaultSaltarelleUIProviderDragData {
		public IDragSource source;
		public IDropTarget target;
		public object sourceData;
		public object targetData;
		public bool completed;
		
		public DefaultSaltarelleUIProviderDragData(IDragSource source, object sourceData) {
			this.source     = source;
			this.sourceData = sourceData;
			this.target     = null;
			this.targetData = null;
			this.completed  = false;
		}
	}
	
	internal delegate DOMElement CreateDraggableHelperDelegate();
#endif

#if SERVER
	[GlobalService(typeof(ISaltarelleUIService))]
#endif
	public class DefaultSaltarelleUIProvider : ISaltarelleUIService
	#if SERVER
	, IGlobalService
	#endif
	{
		private string blankImageUrl;
		public string BlankImageUrl { get { return blankImageUrl; } }

#if SERVER
		public void Setup() {
			blankImageUrl = Saltarelle.Mvc.Routes.GetAssemblyResourceUrl(typeof(Saltarelle.UI.Resources).Assembly, Saltarelle.UI.Resources.BlankImage);
		}
		
		public object ConfigObject {
			get { return new { blankImageUrl }; }
		}
#endif

#if CLIENT
		private const string DragDataKey = "saltarelledragdata";
		private const int DragScrollMargin = 10;
		private const int DragScrollTimeoutInterval = 50;

		public DefaultSaltarelleUIProvider(object config) {
			Dictionary cfg = Dictionary.GetDictionary(config);
			this.blankImageUrl = (string)cfg["blankImageUrl"];
		}
		
		public void RegisterDropTarget(DOMElement element, IDropTarget handler) {
			jQuery el = JQueryProxy.jQuery(element);	// Note that all closures are over the jQuery variable, in order to avoid leaking a reference to a DOMElement.
			JQueryEventHandlerDelegate dragOverHandler = null;
			int scrollIntervalId = -1;
			bool hasLeft = false;

			el.droppable(new Dictionary("tolerance", "pointer",
			                            "greedy",    true,
			                            "over",      (DroppableEventHandlerDelegate)delegate(JQueryEvent overEvt, DroppableEventObject ui) {
			                                             LeftTop cursorPos = null;
			                                             hasLeft = false;
			                                             scrollIntervalId = Window.SetInterval(delegate() {
			                                                                    if (!hasLeft && cursorPos != null) {
			                                                                        if (AutoScroll(el.get(0), cursorPos))
			                                                                            DragOver(el.get(0), handler, ui.draggable, cursorPos);
			                                                                    }
			                                                                }, DragScrollTimeoutInterval);
			                                             dragOverHandler = delegate(JQueryEvent evt) {
			                                                                   if (!hasLeft) {
			                                                                       cursorPos = new LeftTop(evt.pageX, evt.pageY);
			                                                                       DragOver(el.get(0), handler, ui.draggable, cursorPos);
			                                                                   }
			                                                               };
			                                             DefaultSaltarelleUIProviderDragData data = (DefaultSaltarelleUIProviderDragData)ui.draggable.data(DragDataKey);
			                                             handler.DragEnter(data.source, data.sourceData);
			                                             dragOverHandler(overEvt);
			                                             JQueryProxy.jQuery(Window.Document).mousemove(dragOverHandler);
			                                         },
			                            "out",       (DroppableEventHandlerDelegate)delegate(JQueryEvent _, DroppableEventObject ui) {
			                                             DefaultSaltarelleUIProviderDragData data = (DefaultSaltarelleUIProviderDragData)ui.draggable.data(DragDataKey);
			                                             handler.DragLeave(data.source, data.sourceData);
			                                             data.target     = null;
			                                             data.targetData = null;
			                                             hasLeft         = true;
			                                             Window.ClearInterval(scrollIntervalId);
			                                             JQueryProxy.jQuery(Window.Document).unbind("mousemove", dragOverHandler);
			                                         },
			                            "drop",      (DroppableEventHandlerDelegate)delegate(JQueryEvent evt, DroppableEventObject ui) {
			                                             DefaultSaltarelleUIProviderDragData data = (DefaultSaltarelleUIProviderDragData)ui.draggable.data(DragDataKey);
			                                             hasLeft = true;
			                                             if (data.targetData != null) {
			                                                 if (data.source.QueryDrop(data.target, data.sourceData, data.targetData)) {
			                                                     LeftTop dropPos = GetElemRelCoords(el.get(0), new LeftTop(evt.pageX, evt.pageY), true);
			                                                     data.target.Drop(data.source, data.sourceData, data.targetData, dropPos);
			                                                     data.completed = true;
			                                                 }
			                                             }
			                                             Window.ClearInterval(scrollIntervalId);
			                                             JQueryProxy.jQuery(Window.Document).unbind("mousemove", dragOverHandler);
			                                         }));
		}

		public void UnregisterDropTarget(DOMElement element) {
			JQueryProxy.jQuery(element).droppable("destroy");
		}
		
		private LeftTop GetElemRelCoords(DOMElement elem, LeftTop pageRelCoords, bool scrolled) {
			LeftTop elemOffset = JQueryProxy.jQuery(elem).offset();
			return new LeftTop((int)pageRelCoords.left - (int)elemOffset.left + (scrolled ? elem.ScrollLeft : 0), (int)pageRelCoords.top - (int)elemOffset.top + (scrolled ? elem.ScrollTop : 0));
		}
		
		private bool AutoScroll(DOMElement elem, LeftTop pageRelCursorPos) {
			int oldTop = elem.ScrollTop, oldLeft = elem.ScrollLeft;
		
			LeftTop pos = GetElemRelCoords(elem, pageRelCursorPos, false);
			if (pos.left >= 0 && pos.left < DragScrollMargin) {
				elem.ScrollLeft += (int)pos.left - DragScrollMargin;
			}
			else if (pos.left > elem.ClientWidth - DragScrollMargin && pos.left < elem.ClientWidth) {
				elem.ScrollLeft += (int)pos.left - (elem.ClientWidth - DragScrollMargin - 1);
			}

			if (pos.top >= 0 && pos.top < DragScrollMargin) {
				elem.ScrollTop += (int)pos.top - DragScrollMargin;
			}
			else if (pos.top > elem.ClientHeight - 10 && pos.top < elem.ClientHeight) {
				elem.ScrollTop += (int)pos.top - (elem.ClientHeight - DragScrollMargin - 1);
			}
			
			return elem.ScrollTop != oldTop || elem.ScrollLeft != oldLeft;
		}

		private void DragOver(DOMElement droppableElement, IDropTarget handler, jQuery draggable, LeftTop pageCoords) {
			LeftTop pos = GetElemRelCoords(droppableElement, pageCoords, true);

			DefaultSaltarelleUIProviderDragData data = (DefaultSaltarelleUIProviderDragData)draggable.data(DragDataKey);
			object targetData = handler.DragOver(data.source, data.sourceData, pos);

			data.target = handler;
			data.targetData = targetData;
		}
		
		private void DragStop(jQuery element) {
			DefaultSaltarelleUIProviderDragData data = (DefaultSaltarelleUIProviderDragData)element.data(DragDataKey);
			if (data.completed) {
				data.source.DragDropCompleted(data.target, data.sourceData, data.targetData);
			}
			else {
			    if (data.target != null)
			        data.target.DragLeave(data.source, data.sourceData);
			    data.source.DragDropCancelled(data.sourceData);
			}
		}

		public void MakeDraggable(IDragSource source, DOMElement element, object data, DOMElement container) {
			jQuery el = JQueryProxy.jQuery(element);
			el.data(DragDataKey, new DefaultSaltarelleUIProviderDragData(source, data));
			el.draggable(new Dictionary("helper", new CreateDraggableHelperDelegate(delegate {
			                                          DOMElement x = source.CreateDragHelper(data);
			                                          if (Utils.IsNull(x)) {
			                                              jQuery jq = JQueryProxy.jQuery(element);
			                                              x = jq.clone(false).get(0);
			                                          }
			                                          return x;
			                                      }),
			                            "appendTo", JQueryProxy.jQuery(container),
			                            "scroll", false,
			                            "containment", "parent",
			                            "start", new DraggableEventHandlerDelegate(delegate(JQueryEvent evt, DraggableEventObject ui) { source.DragStarted(data, ui.helper.get(0)); }),
			                            "stop", new DraggableEventHandlerDelegate(delegate { DragStop(el); } )));
		}
		
		public void RemoveDraggable(DOMElement element) {
			jQuery el = JQueryProxy.jQuery(element);
			el.removeData(DragDataKey);
			el.draggable("destroy");
		}
#endif
	}
}
